﻿/*!
 * Bootstrap Context Menu
 * Author: @sydcanem
 * https://github.com/sydcanem/bootstrap-contextmenu
 *
 * Inspired by Bootstrap's dropdown plugin.
 * Bootstrap (http://getbootstrap.com).
 *
 * Licensed under MIT
 * ========================================================= */

; (function ($) {

    'use strict';

    /* CONTEXTMENU CLASS DEFINITION
	 * ============================ */
    var toggle = '[data-toggle="context"]';

    var ContextMenu = function (element, options) {
        this.$element = $(element);

        this.before = options.before || this.before;
        this.onItem = options.onItem || this.onItem;
        this.scopes = options.scopes || null;

        if (options.target) {
            this.$element.data('target', options.target);
        }

        this.listen();
    };

    ContextMenu.prototype = {

        constructor: ContextMenu
		, show: function (e) {

		    var $menu
				, evt
				, tp
				, items
				, relatedTarget = { relatedTarget: this };

		    if (this.isDisabled()) return;

		    this.closemenu();

		    if (!this.before.call(this, e, $(e.currentTarget))) return;

		    $menu = this.getMenu();
		    $menu.trigger(evt = $.Event('show.bs.context', relatedTarget));

		    tp = this.getPosition(e, $menu);
		    items = 'li:not(.divider)';
		    $menu.attr('style', '')
				.css(tp)
				.addClass('open')
				.on('click.context.data-api', items, $.proxy(this.onItem, this, $(e.currentTarget)))
				.trigger('shown.bs.context', relatedTarget);

		    // Delegating the `closemenu` only on the currently opened menu.
		    // This prevents other opened menus from closing.
		    $('html')
				.on('click.context.data-api', $menu.selector, $.proxy(this.closemenu, this));

		    return false;
		}

		, closemenu: function (e) {
		    var $menu
				, evt
				, items
				, relatedTarget;

		    $menu = this.getMenu();

		    if (!$menu.hasClass('open')) return;

		    relatedTarget = { relatedTarget: this };
		    $menu.trigger(evt = $.Event('hide.bs.context', relatedTarget));

		    items = 'li:not(.divider)';
		    $menu.removeClass('open')
				.off('click.context.data-api', items)
				.trigger('hidden.bs.context', relatedTarget);

		    $('html')
				.off('click.context.data-api', $menu.selector);
		    // Don't propagate click event so other currently
		    // opened menus won't close.
		    return false;
		}

		, keydown: function (e) {
		    if (e.which == 27) this.closemenu(e);
		}

		, before: function (e) {
		    return true;
		}

		, onItem: function (e) {
		    return true;
		}

		, listen: function () {
		    this.$element.on('contextmenu.context.data-api', this.scopes, $.proxy(this.show, this));
		    $('html').on('click.context.data-api', $.proxy(this.closemenu, this));
		    $('html').on('keydown.context.data-api', $.proxy(this.keydown, this));
		}

		, destroy: function () {
		    this.$element.off('.context.data-api').removeData('context');
		    $('html').off('.context.data-api');
		}

		, isDisabled: function () {
		    return this.$element.hasClass('disabled') ||
					this.$element.attr('disabled');
		}

		, getMenu: function () {
		    var selector = this.$element.data('target')
				, $menu;

		    if (!selector) {
		        selector = this.$element.attr('href');
		        selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
		    }

		    $menu = $(selector);

		    return $menu && $menu.length ? $menu : this.$element.find(selector);
		}

		, getPosition: function (e, $menu) {
		    var mouseX = e.clientX
				, mouseY = e.clientY
				, boundsX = $(window).width()
				, boundsY = $(window).height()
				, menuWidth = $menu.find('.dropdown-menu').outerWidth()
				, menuHeight = $menu.find('.dropdown-menu').outerHeight()
				, tp = { "position": "absolute", "z-index": 9999 }
				, Y, X, parentOffset;

		    if (mouseY + menuHeight > boundsY) {
		        Y = { "top": mouseY - menuHeight + $(window).scrollTop() };
		    } else {
		        Y = { "top": mouseY + $(window).scrollTop() };
		    }

		    if ((mouseX + menuWidth > boundsX) && ((mouseX - menuWidth) > 0)) {
		        X = { "left": mouseX - menuWidth + $(window).scrollLeft() };
		    } else {
		        X = { "left": mouseX + $(window).scrollLeft() };
		    }

		    // If context-menu's parent is positioned using absolute or relative positioning,
		    // the calculated mouse position will be incorrect.
		    // Adjust the position of the menu by its offset parent position.
		    parentOffset = $menu.offsetParent().offset();
		    X.left = X.left - parentOffset.left;
		    Y.top = Y.top - parentOffset.top;

		    return $.extend(tp, Y, X);
		}

    };

    /* CONTEXT MENU PLUGIN DEFINITION
	 * ========================== */

    $.fn.contextmenu = function (option, e) {
        return this.each(function () {
            var $this = $(this)
				, data = $this.data('context')
				, options = (typeof option == 'object') && option;

            if (!data) $this.data('context', (data = new ContextMenu($this, options)));
            if (typeof option == 'string') data[option].call(data, e);
        });
    };

    $.fn.contextmenu.Constructor = ContextMenu;

    /* APPLY TO STANDARD CONTEXT MENU ELEMENTS
	 * =================================== */

    $(document)
	   .on('contextmenu.context.data-api', function () {
	       $(toggle).each(function () {
	           var data = $(this).data('context');
	           if (!data) return;
	           data.closemenu();
	       });
	   })
		.on('contextmenu.context.data-api', toggle, function (e) {
		    $(this).contextmenu('show', e);

		    e.preventDefault();
		    e.stopPropagation();
		});

}(jQuery));